Profile picture

[Ansible] Ansible로 쿠버네티스(k8s) 클러스터 완전 자동 구축하기 (A-Z)

JaehyoJJAng2025년 05월 20일

개요

image

쿠버네티스 클러스터 설치를 Ansible을 사용하여 완전 자동으로 구축하는 방법에 대해서 기록해보려고 합니다.


수동으로 한 단계씩 설치하다보면 실수도 잦고, 여러 노드에 동일한 작업을 반복하는 게 여간 번거로운 일이 아니죠. 😅


이 글에서는 기존의 수동 설치 과정(Swap 비활성화, CRI 설치, kubeadm 초기화 등)을 어떻게 Ansible 플레이북으로 옮기는지,

각 작업의 의미는 무엇인지, 그리고 단계별 검증은 어떻게 하는지 A to Z로 알려드리겠습니다.


이번 게시글을 통해 Ansible로 쿠버네티스 클러스터를 설치하는 과정을 익히고,

이를 발판 삼아 kubespray 라는 프로젝트를 사용하여 더 쉽고 간편하게 쿠버네티스 클러스터를 설치하는 방법에 대해서 추후 다뤄볼 예정입니다.


✔️ 사전 준비 사항

본격적으로 시작하기 전에, 아래 환경이 준비되어 있어야 합니다.

  • 1. Ansible 제어 노드: 플레이북을 실행할 머신입니다. Ansible이 설치되어 있어야 합니다.
  • 2. K8s 클러스터 노드: 최소 2대 이상의 서버 (마스터 1대, 워커 1대 이상). (이 가이드에서는 Ubuntu 22.04 LTS를 기준으로 합니다.)
  • 3. SSH 연결: 제어 노드에서 모든 K8s 노드로 비밀번호 없이 SSH 접속(키 기반 인증)이 가능해야 합니다.
  • 4. Sudo 권한: 모든 K8s 노드에서 sudo 권한을 가진 계정이 필요합니다.

✔️ 앤서블 서버의 /etc/hosts 업데이트

호스트명 기반으로 k8s 서버 및 노드 IP 주소를 매핑하겠습니다.

echo "192.168.219.130 k8s-master" | sudo tee -a /etc/hosts >&/dev/null
echo "192.168.219.131 k8s-node1"  | sudo tee -a /etc/hosts >&/dev/null
echo "192.168.219.132 k8s-node2"  | sudo tee -a /etc/hosts >&/dev/null

📁 프로젝트 구조 및 인벤토리 파일 설정

먼저, 체계적인 관리를 위해 프로젝트 폴더와 인벤토리 파일을 생성해주겠습니다.

mkdir ansible-k8s
cd ansible-k8s
touch inventory k8s-install.yml

inventory 파일은 우리가 관리할 서버들의 목록이죠? 마스터 노드와 워커 노드를 그룹으로 나누어 관리하면 훨씬 편리합니다.

[master]
# 마스터 노드의 IP 주소 또는 호스트 이름을 입력하세요.
k8s-master ansible_host=k8s-master ansible_user=k8s-master

[nodes]
# 워커 노드의 IP 주소 또는 호스트 이름을 입력하세요.
k8s-node1 ansible_host=k8s-node1 ansible_user=k8s-node1
k8s-node2 ansible_host=k8s-node2 ansible_user=k8s-node2

[k8s_cluster:children]
master
nodes
  • ansible_user에는 각 노드에 접속할 사용자 이름을 지정해주시면 됩니다.
  • /etc/hosts 파일에 호스트명과 IP를 매핑해줬다면 ansible_host 부분은 삭제하셔도 무방합니다.

Inventory 통신 확인

인벤토리에 작성된 호스트와 앤서블 서버간 정상적으로 통신이 이루어지는지 테스트해봅시다.

ansible -i inventory.ini k8s_cluster -m ping

k8s-node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
k8s-node2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
k8s-master | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

📖 Ansible 플레이북 작성

참고

플레이북은 모두 Ubuntu 22.04 쿠버네티스 클러스터 구축하기에서 작성했었던 클러스터 구축 순서를 기반으로 작성하였습니다.



이제 본격적으로 플레이북을 작성해보겠습니다.

전체 과정을 3개의 Play 로 나누어 진행할겁니다.


  • 1. Play 1: 모든 노드 (마스터, 워커) 공통 설정
  • 2. Play 2: 마스터 노드 초기화 및 설정
  • 3. Play 3: 워커 노드 클러스터 조인

Play 1: Master & Node 공통 설정

파일 이름: k8s-master-node-install.yml

- name: Play 1 - K8s 클러스터 공통 설정
  hosts: k8s_cluster # inventory에 정의된 k8s_cluster 그룹 실행
  become: true # 모든 작업 sudo 권한으로 실행
  vars:
    K8S_VERSION: "v1.30"

  tasks:
    # --- 1. Swap 비활성화 ---
    - name: "Swap 비활성화 (즉시 적용)"
      command: swapoff -a
      when: ansible_swaptotal_mb > 0 # 스왑이 이미 있을 때만 실행
    
    - name: "fstab에서 swap 설정 제거하기 (재부팅 후에도 설정 유지하기 위해)"
      lineinfile:
        path: /etc/fstab
        regexp: '.*swap.*'
        state: absent # 해당 라인 찾아서 삭제

    # --- 2. 커널 모듈 및 파라미터 설정 ---
    - name: "필요한 커널 모듈 로드 (overlay, br_netfilter)"
      modprobe:
        name: "{{ item }}"
        state: present
      loop:
        - overlay
        - br_netfilter
        # overlay는 컨테이너 파일 시스템을 위해, br_netfilter는 브릿지 네트워크 트래픽을 iptables에서 처리하기 위해 필요합니다.
    
    - name: "쿠버네티스를 위한 sysctl 설정"
      sysctl:
        name: "{{ item.key }}"
        value: "{{ item.value }}"
        sysctl_set: yes
        state: present
        reload: yes
      loop:
        - { key: 'net.bridge.bridge-nf-call-iptables', value: '1' }
        - { key: 'net.ipv4.ip_forward', value: '1' }
        - { key: 'net.bridge.bridge-nf-call-ip6tables', value: '1' }
        # iptables가 브릿지 네트워크를 통과하는 트래픽을 볼 수 있도록 설정합니다. (CNI 동작에 필수)        

    # --- [테스트 1] 커널 모듈 및 sysctl 검증 ---
    - name: "✅ [테스트 1-1] br_netfilter 모듈 검증"
      shell: lsmod | grep br_netfilter
      register: is_br_netfilter
      failed_when: is_br_netfilter.rc != 0

    - name: "✅ [테스트 1-2] overlay 모듈 검증"
      shell: lsmod | grep overlay
      register: is_overlay
      failed_when: is_overlay.rc != 0

    - name: "✅ [테스트 1-3] sysctl 검증"
      shell: |
        sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
      register: is_sysctl
      failed_when:
        - is_sysctl.stdout.find('net.bridge.bridge-nf-call-iptables = 1') == -1
        - is_sysctl.stdout.find('net.bridge.bridge-nf-call-ip6tables = 1') == -1
        - is_sysctl.stdout.find('net.ipv4.ip_forward = 1') == -1

    # --- 3. CRI 설치 (Containerd) ---
    - name: "Docker GPG 키 추가"
      apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present

    - name: "Docker APT 저장소 추가"
      apt_repository:
        repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
        state: present

    - name: "Containerd 설치"
      apt:
        name: containerd.io
        state: present

    - name: "Containerd 설정 파일 디렉터리 생성"
      file:
        path: /etc/containerd
        state: directory

    - name: "Containerd 기본 설정 파일 생성 및 SystemdCgroup 활성화"
      shell: |
        containerd config default > /etc/containerd/config.toml
        sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
      # 설명: 쿠버네티스의 kubelet은 Systemd로 cgroup을 관리하므로, 컨테이너 런타임도 동일하게 맞춰줘야 합니다. (매우 중요!)

    - name: "Containerd 서비스 재시작 및 활성화"
      systemd:
        name: containerd
        state: restarted
        enabled: yes

    # --- [테스트 2] Containerd 상태 확인 ---
    - name: "✅ [테스트 2] Containerd 서비스 상태 확인"
      command: systemctl is-active containerd
      register: containerd_status
      changed_when: false
    - debug:
        var: containerd_status.stdout
      # 실행 결과가 "active"이면 성공입니다.

    # --- 4. 쿠버네티스 구성 요소 설치 (kubelet, kubeadm, kubectl) ---
    - name: Update apt package index
      ansible.builtin.apt:
        update_cache: yes

    - name: "필수 패키지 설치"
      ansible.builtin.apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - gpg
        state: present

    - name: "apt keyrings 폴더 생성"
      ansible.builtin.file:
        path: /etc/apt/keyrings
        state: directory
        mode: '0755'

    - name: "Kubernetes GPG 키 추가"
      ansible.builtin.shell: "curl -fsSL https://pkgs.k8s.io/core:/stable:/{{ K8S_VERSION }}/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg"
      args:
        creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 파일이 이미 있으면 실행 안 함

    - name: "Kubernetes APT 저장소 추가"
      apt_repository:
        repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/{{ K8S_VERSION }}/deb/ /"
        state: present
        filename: kubernetes

    - name: Update apt package index again
      ansible.builtin.apt:
        update_cache: yes

    - name: "kubelet, kubeadm, kubectl 설치"
      apt:
        name: ['kubelet', 'kubeadm', 'kubectl']
        state: present
        update_cache: yes

    - name: "Kube 패키지 버전 고정 (자동 업데이트 방지)"
      dpkg_selections:
        name: "{{ item }}"
        selection: hold
      loop:
        - kubelet
        - kubeadm
        - kubectl
      # 설명: 클러스터의 안정성을 위해 쿠버네티스 컴포넌트 버전이 예기치 않게 변경되는 것을 막습니다.

    - name: "kubelet 서비스 시작"
      ansible.builtin.systemd:
        name: kubelet
        enabled: yes
        state: started

플레이북 실행하기

ansible-playbook -i inventory.ini k8s_cluster -K

구간 테스트

1. 스왑 설정

image


2. 네트워크 설정

image


3. Containerd 설치

image


4. kubelet, kubeadm, kubectl 설치

image


Play 2: 마스터 노드 설정

이제 컨트롤 플레인을 담당할 마스터 노드를 초기화하겠습니다.


파일 이름: master.yml

- name: Play 2 - K8s 마스터 노드 설정
  hosts: master
  become: yes
  
  tasks:
    # --- 1. 컨트롤 플레인 초기화 ---
    - name: "kubeadm으로 클러스터 초기화"
      command: "kubeadm init --pod-network-cidr=192.168.0.0/16"
      args:
        creates: /etc/kubernetes/admin.conf # 이 파일이 이미 있다면, 초기화가 완료된 것으로 간주하고 이 작업을 건너뜁니다.
      register: kubeadm_init_result
      # 설명: --pod-network-cidr는 파드(Pod)들이 사용할 내부 IP 대역입니다. CNI 플러그인(여기서는 Flannel/Calico 기준)과 일치해야 합니다.

    - name: "초기화 결과 출력 (Join 명령어 확인용)"
      debug:
        var: kubeadm_init_result.stdout_lines

    # --- 2. kubectl 사용을 위한 환경 설정 ---
    - name: "kubectl 설정을 위한 .kube 디렉터리 생성"
      file:
        path: "/home/{{ ansible_user }}/.kube" # ubuntu 사용자의 홈 디렉터리
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"

    - name: "마스터 노드의 admin.conf 파일을 사용자의 .kube/config로 복사"
      copy:
        src: /etc/kubernetes/admin.conf
        dest: "/home/{{ ansible_user }}/.kube/config"
        remote_src: yes # 원격지(마스터 노드)에 있는 파일을 복사합니다.
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"

    # --- 3. CNI(Container Network Interface) 설치 (Calico) ---
    - name: "네트워크 플러그인(Calico) 설치"
      become: false # kubectl은 일반 사용자 권한으로 실행
      command: "kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml"
      args:
        chdir: "/home/{{ ansible_user }}" # kubectl이 config 파일을 찾을 수 있도록 홈 디렉터리에서 실행
      # 설명: 파드 간의 통신을 가능하게 하는 CNI를 설치합니다. Calico는 강력한 네트워크 정책 기능을 제공합니다.

    # --- [테스트 3] 마스터 노드 상태 확인 ---
    - name: "✅ [테스트] 1분 대기 후 노드 상태 확인 (CNI Pod 준비 시간)"
      pause:
        minutes: 1

    - name: "✅ [테스트] kubectl get nodes 실행"
      become: false
      command: "kubectl get nodes"
      register: kubectl_get_nodes
      changed_when: false
    - debug:
        var: kubectl_get_nodes.stdout
      # 실행 결과에서 마스터 노드의 STATUS가 'Ready'로 표시되면 성공입니다.

Play 3: 워커 노드 클러스터 조인

클러스터 조인 부분은 수동으로 진행하겠습니다 ^^


Play 2를 실행하면 다음과 같이 kubeadm join이 나올겁니다.

그 명령을 노드에 각각 접속하여 조인시켜주시면 됩니다.

    Tag -

Loading script...